package top.tresman.cxxcommon.utils;

import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class RedisUtils {

    private static final Long DEFAULT_EXPIRE_SECONDS = 7200L;

    private static final Long DEFAULT_LOCK_OVERTIME = 10L;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    public <T> T get(String k, Class<T> clazz) {
        Object o = redisTemplate.opsForValue().get(k);
        return clazz.cast(o);
    }

    public void set(String k, Object obj) {
        redisTemplate.opsForValue().set(k, obj, DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS);
    }

    public void set(String k, Object obj, Long seconds) {
        if (seconds == -1) {
            redisTemplate.opsForValue().set(k, obj);
            return;
        }

        redisTemplate.opsForValue().set(k, obj, seconds, TimeUnit.SECONDS);
    }

    public Boolean exists(String k) {
        return redisTemplate.hasKey(k);
    }

    public Long ttl(String k) {
        return redisTemplate.getExpire(k);
    }

    public void expire(String k, Long seconds) {
        redisTemplate.expire(k, seconds, TimeUnit.SECONDS);
    }

    public void expireAt(String k, Date expireAt) {
        redisTemplate.expireAt(k, expireAt);
    }

    public Long incr(String k) {
        return redisTemplate.opsForValue().increment(k);
    }

    public Long incr(String k, Long delta) {
        return redisTemplate.opsForValue().increment(k, delta);
    }

    public Long decr(String k) {
        return redisTemplate.opsForValue().decrement(k);
    }

    public Long decr(String k, Long delta) {
        return redisTemplate.opsForValue().decrement(k, delta);
    }

    public void delete(String k) {
        redisTemplate.delete(k);
    }

    public boolean lock(String lockKey, Long value) {
        return lock(lockKey, value, DEFAULT_LOCK_OVERTIME);
    }

    public boolean lock(String lockKey, Long value, Long overtime) {
        if (overtime == null) {
            overtime = DEFAULT_LOCK_OVERTIME;
        }
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(lockKey, value, overtime, TimeUnit.SECONDS);

        return Boolean.TRUE.equals(isLock);
    }

    public void unlock(String lockKey, Long value) {
        Long oldValue = get(lockKey, Long.class);
        if (oldValue == null) {
            return;
        }

        if (!value.equals(oldValue)) {
            return;
        }

        delete(lockKey);
    }

    public <T> T hGet(String key, String hashKey, Class<T> clazz) {
        Object o = redisTemplate.opsForHash().get(key, hashKey);
        return clazz.cast(o);
    }

    public void hSet(String key, String hashKey, Object obj) {
        redisTemplate.opsForHash().put(key, hashKey, obj);
    }

    public <T> List<T> hValues(String key, Class<T> clazz) {
        List<T> res = new ArrayList<>();
        List<Object> values = redisTemplate.opsForHash().values(key);
        if (CollectionUtils.isEmpty(values)) {
            return res;
        }

        for (Object value : values) {
            res.add(clazz.cast(value));
        }

        return res;
    }

    @SuppressWarnings("ConfusingArgumentToVarargsMethod")
    public void hDel(String key, String... hashKey) {
        redisTemplate.opsForHash().delete(key, hashKey);
    }

    public Cursor<Map.Entry<Object, Object>> hScan(String key) {
        return redisTemplate.opsForHash().scan(key, ScanOptions.NONE);
    }

    public Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions scanOptions) {
        return redisTemplate.opsForHash().scan(key, scanOptions);
    }

    public Boolean hExists(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    public Set<String> hKeys(String key) {
        Set<Object> keys = redisTemplate.opsForHash().keys(key);
        if (CollectionUtils.isEmpty(keys)) {
            return null;
        }

        return keys.stream().map(Object::toString).collect(Collectors.toSet());
    }

    public <T> List<T> hMGet(String key, Collection<String> hashKeyList, Class<T> clazz) {
        if (CollectionUtils.isEmpty(hashKeyList)) {
            return null;
        }
        List<Object> list = hashKeyList.stream().map(d -> (Object) d).collect(Collectors.toList());
        List<Object> objectList = redisTemplate.opsForHash().multiGet(key, list);
        if (CollectionUtils.isEmpty(objectList)) {
            return null;
        }

        return objectList.stream().map(clazz::cast).collect(Collectors.toList());
    }

    public void hMSet(String key, Map<String, Object> keyValueMap) {
        redisTemplate.opsForHash().putAll(key, keyValueMap);
    }

    public Long hLen(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    public void lPush(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }

    public <T> T lPop(String key, Class<T> clazz) {
        return clazz.cast(redisTemplate.opsForList().leftPop(key));
    }

    public void lSet(String key, Long index, Object value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    public void rPush(String key, Object value) {
        redisTemplate.opsForList().rightPush(key, value);
    }

    public <T> T rPop(String key, Class<T> clazz) {
        return clazz.cast(redisTemplate.opsForList().rightPop(key));
    }

    public void lInsert(String key, Object pivot, Object value) {
        redisTemplate.opsForList().leftPush(key, pivot, value);
    }

    public void lTrim(String key, Long start, Long end) {
        redisTemplate.opsForList().trim(key, start, end);
    }

    public Long lLen(String key) {
        return redisTemplate.opsForList().size(key);
    }

    public void lRem(String key, Object value) {
        redisTemplate.opsForList().remove(key, 1, value);
    }

    public void sAdd(String key, Object... obj) {
        redisTemplate.opsForSet().add(key, obj);
    }

    public Boolean sIsMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    public <T> Set<T> sMembers(String key, Class<T> clazz) {
        Set<Object> members = redisTemplate.opsForSet().members(key);
        if (CollectionUtils.isEmpty(members)) {
            return new HashSet<>();
        }

        return members.stream().map(clazz::cast).collect(Collectors.toSet());
    }

    public void sRem(String key, Object... obj) {
        redisTemplate.opsForSet().remove(key, obj);
    }

    public Cursor<Object> sScan(String key) {
        return redisTemplate.opsForSet().scan(key, ScanOptions.NONE);
    }

    public Cursor<Object> sScan(String key, ScanOptions scanOptions) {
        return redisTemplate.opsForSet().scan(key, scanOptions);
    }

    public Long sCard(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    public <T> Set<T> sDiff(String key, Collection<String> otherKeys, Class<T> clazz) {
        Set<Object> difference = redisTemplate.opsForSet().difference(key, otherKeys);
        if (difference == null) {
            return null;
        }

        return difference.stream().map(clazz::cast).collect(Collectors.toSet());
    }

    public void sDiffStore(String key, Collection<String> otherKeys, String destKey) {
        redisTemplate.opsForSet().differenceAndStore(key, otherKeys, destKey);
    }

    public <T> Set<T> sInter(String key, Collection<String> otherKeys, Class<T> clazz) {
        Set<Object> difference = redisTemplate.opsForSet().intersect(key, otherKeys);
        if (difference == null) {
            return null;
        }

        return difference.stream().map(clazz::cast).collect(Collectors.toSet());
    }

    public void sInterStore(String key, Collection<String> otherKeys, String destKey) {
        redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey);
    }

    public <T> Set<T> sUnion(String key, Collection<String> otherKeys, Class<T> clazz) {
        Set<Object> difference = redisTemplate.opsForSet().union(key, otherKeys);
        if (difference == null) {
            return null;
        }

        return difference.stream().map(clazz::cast).collect(Collectors.toSet());
    }

    public void sUnionStore(String key, Collection<String> otherKeys, String destKey) {
        redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
    }

    public void zAdd(String key, Object value, Double score) {
        redisTemplate.opsForZSet().add(key, value, score);
    }

    public Long zCard(String key) {
        return redisTemplate.opsForZSet().size(key);
    }

    public Long zCount(String key, Double min, Double max) {
        return redisTemplate.opsForZSet().count(key, min, max);
    }

    public Double zIncrBy(String key, Object member, Double increment) {
        return redisTemplate.opsForZSet().incrementScore(key, member, increment);
    }

    public <T> Set<T> zRange(String key, Long start, Long end, Class<T> clazz) {
        Set<Object> range = redisTemplate.opsForZSet().range(key, start, end);
        if (range == null) {
            return null;
        }

        return range.stream().map(clazz::cast).collect(Collectors.toSet());
    }

    /**
     * 按分值升序获取成员和分值
     *
     * @param key
     * @param start
     * @param end
     * @param clazz
     * @author cxx
     * @date 2023/11/9 17:46
     * @desc
     */
    public <T> Set<ZSetOperations.TypedTuple<T>> zRangeWithScores(String key, Long start, Long end, Class<T> clazz) {
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().rangeWithScores(key, start, end);
        if (typedTuples == null) {
            return null;
        }

        return typedTuples.stream().map(d -> ZSetOperations.TypedTuple.of(Objects.requireNonNull(clazz.cast(d.getValue())), d.getScore())).collect(Collectors.toSet());
    }

    /**
     * 获取分值处于 min 和 max 之间的成员（闭区间，包含 min 和 max）
     *
     * @param null
     * @author cxx
     * @date 2023/11/10 9:37
     * @desc
     */
    public <T> Set<T> zRangeByScore(String key, Double min, Double max, Class<T> clazz) {
        Set<Object> range = redisTemplate.opsForZSet().rangeByScore(key, min, max);
        if (range == null) {
            return null;
        }

        return range.stream().map(clazz::cast).collect(Collectors.toSet());
    }

    public <T> Set<ZSetOperations.TypedTuple<T>> zRangeByScoreWithScores(String key, Long start, Long end, Class<T> clazz) {
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().rangeByScoreWithScores(key, start, end);
        if (typedTuples == null) {
            return null;
        }

        return typedTuples.stream().map(d -> ZSetOperations.TypedTuple.of(Objects.requireNonNull(clazz.cast(d.getValue())), d.getScore())).collect(Collectors.toSet());
    }

    public Long zRank(String key, Object value) {
        Long rank = redisTemplate.opsForZSet().rank(key, value);
        if (rank == null) {
            return null;
        }

        return rank + 1;
    }

    public void zRem(String key, Object... values) {
        redisTemplate.opsForZSet().remove(key, values);
    }

    public void zRemRange(String key, Long start, Long end) {
        redisTemplate.opsForZSet().removeRange(key, start, end);
    }

    public void zRemRangeByScore(String key, Double min, Double max) {
        redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
    }

    public <T> Set<T> zRevRange(String key, Long start, Long end, Class<T> clazz) {
        Set<Object> range = redisTemplate.opsForZSet().reverseRange(key, start, end);
        if (range == null) {
            return null;
        }

        return range.stream().map(clazz::cast).collect(Collectors.toSet());
    }

    /**
     * 按分值升序获取成员和分值
     *
     * @param key
     * @param start
     * @param end
     * @param clazz
     * @author cxx
     * @date 2023/11/9 17:46
     * @desc
     */
    public <T> Set<ZSetOperations.TypedTuple<T>> zRevRangeWithScores(String key, Long start, Long end, Class<T> clazz) {
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
        if (typedTuples == null) {
            return null;
        }

        return typedTuples.stream().map(d -> ZSetOperations.TypedTuple.of(Objects.requireNonNull(clazz.cast(d.getValue())), d.getScore())).collect(Collectors.toSet());
    }

    /**
     * 获取分值处于 min 和 max 之间的成员（闭区间，包含 min 和 max）
     *
     * @param null
     * @author cxx
     * @date 2023/11/10 9:37
     * @desc
     */
    public <T> Set<T> zRevRangeByScore(String key, Double min, Double max, Class<T> clazz) {
        Set<Object> range = redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
        if (range == null) {
            return null;
        }

        return range.stream().map(clazz::cast).collect(Collectors.toSet());
    }

    public <T> Set<ZSetOperations.TypedTuple<T>> zRevRangeByScoreWithScores(String key, Long start, Long end, Class<T> clazz) {
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, start, end);
        if (typedTuples == null) {
            return null;
        }

        return typedTuples.stream().map(d -> ZSetOperations.TypedTuple.of(Objects.requireNonNull(clazz.cast(d.getValue())), d.getScore())).collect(Collectors.toSet());
    }

    public Long zRevRank(String key, Object value) {
        Long rank = redisTemplate.opsForZSet().reverseRank(key, value);
        if (rank == null) {
            return null;
        }

        return rank + 1;
    }

    public Double zScore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }

    public Cursor<ZSetOperations.TypedTuple<Object>> zScan(String key) {
        return redisTemplate.opsForZSet().scan(key, ScanOptions.NONE);
    }
}
